package dbManager;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

/**
 *This class is responsible to update DB and for process queries  
 */
public class Manager extends Observable
{
	
	static private Manager instance = null;
	//for singleton
	private static Object someObject = new Object();

	private Manager(){}
	

	static public Manager getInstance() 
	{
		if(null == instance) 
		{
			//to insure that the time slice doesn't cause to creation of 2 instances
			synchronized (someObject) 
			{
				if (null == instance)
					instance = new Manager();
			}
		}
	    return instance;
	}
	
	/**
	 * Updating the DB with the new tags 
	 * @param tags are the tags we want to insert to DB
	 * @param name is the name of the file we want to insert to DB
	 * @param path is the path of the file 
	 * @param new_file true - if we want to add new file to DB. 
	 * 		  new_file false - if we want to delete a file which exists in DB 
	 */
	public void updateDB(String[] tags, String name, String path, boolean new_file)
	{
		File sessionFile = new File("C:\\Program Files\\m&l_dropbox\\hibernate.cfg.xml");
		/*creating session for connecting to DB with hibernate*/
		SessionFactory sessionFactory = new Configuration().configure(sessionFile).buildSessionFactory(); 
		Session session = sessionFactory.openSession();
		TagManager manager = new TagManager(session);
		//new file was added,therefore we want to insert the new file and tags to DB
		if (new_file)
		{
			/*updating DB with the new tags"*/

			//run over all the tags which we want to insert DB
			for (int i = 0; i < tags.length; i++)
			{
				if(tags[i] != null)
				{
					Tag tag = new Tag();
					tag.setFileName(name);
					tag.setTag(tags[i]);
					tag.setPath(path);
					//Insertion of the new tag to DB
					manager.saveTag(tag);
				}
			}
		}
		//deleting related file's tags
		else 
		{
			/*run over all the tuples in DB and remove the related file's tags*/
			
			Query query = session.createQuery("from dbManager.Tag");
			@SuppressWarnings("unchecked")
			List <Tag>list = query.list();
			Iterator<Tag> it=list.iterator();
			Tag tag;
			//running over all the tuples in DB 'tags'
			while (it.hasNext())
			{
				tag=it.next();
				//this tag is of the file that was deleted
				if (tag.getFileName().equals(name) && tag.getPath().equals(path))
				{
					//deleing the tuple with this tag
					manager.deleteTag(tag);
				}			
			}
		}
		session.flush();
		setChanged();
		//we notify the GUI that there is change in the list of the tags
		notifyObservers();
	}
	
	/**
	 * @param tag is the tag we want its index in the list tagsData
	 * @param tagsData is array list of TagFiles
	 * @return the index of the tag in the array list 'tagsData'
	 */
	public int indexOfTag(String tag,ArrayList<TagFiles> tagsData)
	{
		int index = 0;
		//run over the list tagsData
		for (index = 0; index < tagsData.size(); index++) 
		{
			//the case we found the tag in the list tagsData.
			if (tagsData.get(index).getTag().equals(tag))
				//return the index of the tag in the list
				return index;
		}
		//the tag wasn't found in the list
		return -1;
	}
	
	/**
	 * @param path is the file we want its index in the list filesData
	 * @param filesData is array list of FileTags
	 * @return the index of the file in the array list 'filesData'
	 */
	public int indexOfPath(String path,ArrayList<FileTags> filesData)
	{
		int index = 0;
		//run over the list filesData
		for (index = 0; index < filesData.size(); index++) 
		{
			//the case we found the file in the list filesData.
			if (filesData.get(index).getPath().equals(path))
				//return the index of the file in the list
				return index;
		}
		//the file wasn't found in the list
		return -1;
	}
	
	/**
	 * @param tags is the query of tags the user inserted in the GUI
	 * @param file_text is the return value.
	 * 		   it contains the files' paths which suitable to the query of tags the user inserted in the GUI
	 */
	public void getTheTags(String tags, Text file_text)
	{
		int i,j;
		File sessionFile = new File("C:\\Program Files\\m&l_dropbox\\hibernate.cfg.xml");
		
		/*creating session for connecting to DB with hibernate*/
		SessionFactory sessionFactory = new Configuration().configure(sessionFile).buildSessionFactory(); 
		Session session = sessionFactory.openSession();
		
		TagManager manager = new TagManager(session);
		Query query = session.createQuery("from dbManager.Tag");
		List <Tag>list = query.list();
		ArrayList<FileTags> filesData = new ArrayList<FileTags>();
		ArrayList<TagFiles> tagsData = new ArrayList<TagFiles>();
		Iterator<Tag> it=list.iterator();
		Tag tag;
		TagFiles tagFiles;
		FileTags fileTags;
		//run over the tuples in the DB tags
		while (it.hasNext())
		{
			tag=it.next();
			i = indexOfTag(tag.getTag(), tagsData);
			j = indexOfPath(tag.getPath(), filesData);
			//the tag doesn't exist in the list tagsData
			if (i == -1)
			{
				//creating new instance of the class tagFiles
				tagFiles = new TagFiles(tag.getTag());
				//update tagFiles with the suitable path
				tagFiles.addPath(tag.getPath());
				//add to the array list the new instance
				tagsData.add(tagFiles);
			}
			//the tag exist in the list tagsData
			else
			{
				tagFiles = tagsData.get(i);
				//update the path the tag exist
				tagFiles.addPath(tag.getPath());
			}
			//the file doesn't exist in the list filesData
			if (j == -1)
			{
				//creating new instance of the class fileTags
				fileTags = new FileTags(tag.getFileName(), tag.getPath()); 
				//update fileTags with the suitable tag
				fileTags.addTag(tag.getTag());
				//add to the array list the new instance
				filesData.add(fileTags);		
			}
			//the file exist in the list filesData
			else
			{
				fileTags = filesData.get(j);
				//update the suitable fileTags with the new tag 
				fileTags.addTag(tag.getTag());
			}
		}
		//paths is a list of paths of files which suitable to the query 'tags' 
		ArrayList<String> paths = recQueryParser(tags, filesData, tagsData);
		//appending the paths to file_text
		for (int k = 0; k < paths.size(); k++) 
		{ 
			file_text.append(paths.get(k)+"\n");
		}
		session.flush();
	}

	/**
	 * Empty the table 'tags' 
	 */
	public void truncDB()
	{
		File sessionFile = new File("C:\\Program Files\\m&l_dropbox\\hibernate.cfg.xml");
		
		/*creating session for connecting to DB with hibernate*/
		SessionFactory sessionFactory = new Configuration().configure(sessionFile).buildSessionFactory(); 
		Session session = sessionFactory.openSession();
		
		TagManager manager = new TagManager(session);
		Query query = session.createQuery("from dbManager.Tag");
		List <Tag>list = query.list();
		Iterator<Tag> it=list.iterator();
		Tag tag;
		//run over all the tuples and delete each one
		while (it.hasNext())
		{
			tag=it.next();
			manager.deleteTag(tag);		
		}
		session.flush();
	}

	/**
	 * This function update the table tags with the tags which exist in DB
	 * @param tags is the table where the tags will be seen
	 */
	public void getUpdate(Table tags) 
	{
		File sessionFile = new File("C:\\Program Files\\m&l_dropbox\\hibernate.cfg.xml");
		/*creating session for connecting to DB with hibernate*/
		SessionFactory sessionFactory = new Configuration().configure(sessionFile).buildSessionFactory(); 
		Session session = sessionFactory.openSession();
		
		Query query = session.createQuery("from dbManager.Tag");
		ArrayList<String> tags_list = new ArrayList<String>();
		List <Tag>list = query.list();
		Iterator<Tag> it=list.iterator();
		Tag tag;
		while (it.hasNext())
		{
			tag=it.next();
			//the tag doesn't exist in the list tags_list yet
			if(first_time(tag.getTag(), tags_list))
			{
				//creating the table item and insert the new tag to the table item
				TableItem item = new TableItem(tags, SWT.None);
				item.setText(tag.getTag());
				
				//adding the tag to the list
				tags_list.add(tag.getTag());
			}
		}
		session.flush();
	}

	/**
	 * @param tag 
	 * @param tags_list is list of tags
	 * @return false - if the tag exists in the list tags_list. else -return true 
	 */
	private boolean first_time(String tag, ArrayList<String> tags_list)
	{
		for (int i = 0; i < tags_list.size(); i++) 
		{
			if(tag.equals(tags_list.get(i)))
			{
				return false;
			}
		}
		return true;
	}
	
	/**
	 * @param query - is a query with tags from the GUI
	 * @param fileTags - is a list of FileTags (list of files and for each file there 
	 * 												is a list with the tags of this file)
	 * @param tagFiles - is a list of TagFiles (list of tags and for each tag there 
	 * 												is a list with the suitable files)
	 * @return list of files which suitable to the tags in the query
	 */
	public 	ArrayList<String> recQueryParser(String query,ArrayList<FileTags> fileTags,ArrayList<TagFiles> tagFiles)
	{
		int par_index_start = query.indexOf("(");
		int par_index_end;
		int and_index = query.indexOf("&&");
		int not_index = query.indexOf("~");
		int or_index = query.indexOf("||");
		ArrayList<String> results;
		//the list of files we return from the function 
		ArrayList<String> real_result = new ArrayList<String>();
		
		String str;
		//the case there is opening parenthesis in the query
		if (par_index_start != -1)
		{
			//check the suitable closing parenthesis 
			par_index_end = count_parenthesis(query, par_index_start);
			//the sub query inside the parenthesis
			str = query.substring(par_index_start+1,par_index_end); 
			//check the list of paths which suitable to the query inside the parenthesis
			results = recQueryParser(str, fileTags, tagFiles);
			// the case that this is a single tag inside parenthesis
			if (and_index == -1 && not_index == -1 && or_index == -1)
			{
				real_result = results;
			}
			else
			{
				//the case that there is not sign inside 'str' (inside the expression in the parenthesis)
				if((not_index > par_index_start && not_index < par_index_end))
				{
					not_index = -1;
				}
				//the case that there is and sign inside 'str' (inside the expression in the parenthesis)
				if((and_index > par_index_start && and_index < par_index_end))
				{
					and_index = -1;
				}
				//the case that there is or sign inside 'str' (inside the expression in the parenthesis)
				if((or_index > par_index_start && or_index < par_index_end))
				{
					or_index = -1;
				}
				
				//the case there is no special sign outside the parenthesis and next to them
				if(not_index == -1 && and_index == -1 && or_index == -1)
				{
					real_result = results;
				}
				else
				{
					//there is not sign close to the parenthesis but not inside them
					if(not_index != -1 && and_index == -1 && or_index == -1)
					{
						//the not sign is close to the opening parenthesis
						if(query.charAt(not_index + 1) == '(')
						{
							FileTags temp;
							String file;
							//running over all the files 
							for (int i = 0; i < fileTags.size(); i++) 
							{
								temp = fileTags.get(i);
								file = temp.getPath();
								//the case the file doesn't exist in results
								if (!results.contains(file))
								{
									//we add the file to the return value
									real_result.add(file);
								}
							}
						}
					}
					else
					{
						ArrayList<String> result1;
						ArrayList<String> result2;
						//there is  and sign and or sign outside the parenthesis
						if (and_index != -1 && or_index != -1)
						{
							//the or sign come first in the query
							if (and_index > or_index)
							{
								/*we split the query to 2 queries. the divider is '||'
								 * and analyze each query separately*/
								String part1 = query.substring(0, query.indexOf("||"));
								String part2 = query.substring(query.indexOf("||")+2);
								result1 = recQueryParser(part1, fileTags, tagFiles);
								result2 = recQueryParser(part2, fileTags, tagFiles);
								//merging the results of the queries
								real_result = mergeAccordingOr(result1,result2);
							}
							//the and sign come first in the query
							else
							{
								/*we split the query to 2 queries. the divider is '&&'
								 * and analyze each query separately*/
								String part1 = query.substring(0, query.indexOf("&&"));
								String part2 = query.substring(query.indexOf("&&")+2);
								result1 = recQueryParser(part1, fileTags, tagFiles);
								result2 = recQueryParser(part2, fileTags, tagFiles);
								//merging the results of the queries
								real_result = mergeAccordingAnd(result1,result2);
							}
						}//end if (and_index != -1 && or_index != -1)
						else
						{
							//the case there is just and sign
							if (and_index != -1)
							{
								/*we split the query to 2 queries. the divider is '&&'
								 * and analyze each query separately*/
								String part1 = query.substring(0, query.indexOf("&&"));
								String part2 = query.substring(query.indexOf("&&")+2);
								result1 = recQueryParser(part1, fileTags, tagFiles);
								result2 = recQueryParser(part2, fileTags, tagFiles);
								real_result = mergeAccordingAnd(result1,result2);
							}
							//the case there is just and sign
							else
							{
								/*we split the query to 2 queries. the divider is '||'
								 * and analyze each query separately*/
								String part1 = query.substring(0, query.indexOf("||"));
								String part2 = query.substring(query.indexOf("||")+2);
								result1 = recQueryParser(part1, fileTags, tagFiles);
								result2 = recQueryParser(part2, fileTags, tagFiles);
								real_result = mergeAccordingOr(result1,result2);
							}
						}	

					}
				}
			}
		}//end if (par_index_start != -1)
		else
		{
			//a single tag case
			if (and_index == -1 && not_index == -1 && or_index == -1)
			{
				//searching in the list tagFiles the tag in the query
				for (int i = 0; i < tagFiles.size(); i++) 
				{
					if (query.equals(tagFiles.get(i).getTag()))
					{
						real_result = tagFiles.get(i).getPaths();
					}
				}		
			}
			//not a single tag
			else
			{
				//the case that this is ~ and a single tag
				if (not_index != -1 && and_index == -1 && or_index == -1)
				{
					FileTags temp;
					
					//run over all the files and insert to return value(real-result) the files which have 
					//tags different from the tag in the query
					for (int i = 0; i < fileTags.size(); i++) 
					{
						temp = fileTags.get(i);
						if (!temp.doesContain(query.substring(not_index+1)))
						{
							real_result.add(temp.getPath());
						}
					}
				}
				else
				{
					ArrayList<String> result1;
					ArrayList<String> result2;
					//the case that there is or sign and and sign in the query
					if (and_index != -1 && or_index != -1)
					{
						//or sign before an sign
						if (and_index>or_index)
						{
							/*we split the query to 2 queries. the divider is '||'
							 * and analyze each query separately*/
							String part1 = query.substring(0, query.indexOf("||"));
							String part2 = query.substring(query.indexOf("||")+2);
							result1 = recQueryParser(part1, fileTags, tagFiles);
							result2 = recQueryParser(part2, fileTags, tagFiles);
							//merging the results
							real_result = mergeAccordingOr(result1,result2);
						}
						//and sign before or sign
						else
						{
							/*we split the query to 2 queries. the divider is '&&'
							 * and analyze each query separately*/
							String part1 = query.substring(0, query.indexOf("&&"));
							String part2 = query.substring(query.indexOf("&&")+2);
							result1 = recQueryParser(part1, fileTags, tagFiles);
							result2 = recQueryParser(part2, fileTags, tagFiles);
							//merging the results
							real_result = mergeAccordingAnd(result1,result2);
						}
					}
					//there is just or sign or and sign
					else
					{ 
						//there is and sign
						if (and_index != -1)
						{
							/*we split the query to 2 queries. the divider is '&&'
							 * and analyze each query separately*/
							String part1 = query.substring(0, query.indexOf("&&"));
							String part2 = query.substring(query.indexOf("&&")+2);
							result1 = recQueryParser(part1, fileTags, tagFiles);
							result2 = recQueryParser(part2, fileTags, tagFiles);
							//merging the results
							real_result = mergeAccordingAnd(result1,result2);
						}
						// there is or sign
						else
						{
							/*we split the query to 2 queries. the divider is '||'
							 * and analyze each query separately*/
							String part1 = query.substring(0, query.indexOf("||"));
							String part2 = query.substring(query.indexOf("||")+2);
							result1 = recQueryParser(part1, fileTags, tagFiles);
							result2 = recQueryParser(part2, fileTags, tagFiles);
							//merging the results
							real_result = mergeAccordingOr(result1,result2);
						}
					}	
				}				
			}
		}
		return real_result;
	}

	/**
	 * The function merge between the 2 lists result1 and result2
	 * @param result1 is a list of strings 
	 * @param result2 is a list of strings 
	 * @return list of the strings which exists in result1 and/or in result2
	 */
	public ArrayList<String> mergeAccordingOr(ArrayList<String> result1, ArrayList<String> result2) 
	{
		ArrayList<String> result = new ArrayList<String>();
		//add all the strings from result1
		for (int i = 0; i < result1.size(); i++) 
		{
			result.add(result1.get(i));
		}
		//add the strings which were not added in result1
		for (int i = 0; i < result2.size(); i++) 
		{
			if (!result.contains(result2.get(i)))
			{
				result.add(result2.get(i));
			}
		}
		return result;
	}

	/**
	 * The function merge between the 2 lists result1 and result2
	 * @param result1 is a list of strings 
	 * @param result2 is a list of strings 
	 * @return list of the strings which exists in result1 and in result2
	 */
	public ArrayList<String> mergeAccordingAnd(ArrayList<String> result1, ArrayList<String> result2) 
	{
		ArrayList<String> result = new ArrayList<String>();
		//add each string from result1 just if this string exists also in result2 
		for (int i = 0; i < result1.size(); i++) 
		{
			if (result2.contains(result1.get(i)))
			{
				result.add(result1.get(i));
			}
		}
		return result;
	}

	/**
	 * The function check where is the index of the closing parenthesis
	 * @param buffer is a string
	 * @param start_index is the index of the opening parenthesis in buffer
	 * @return the index where is the closing parenthesis
	 */
	public int count_parenthesis(String buffer,int start_index)
	{
		int counter = 1;
		int index = start_index;
		//if counter is not zero it's a sign that we didn't find yet the closing parenthesis
		while (counter != 0)
		{
			index++;
			if (buffer.charAt(index) == '(')
			{
				counter++;
			}
			if (buffer.charAt(index) == ')')
			{
				counter--;	
			}
		}
		return index;	
	}
	
	/**
	 * This function check for each tuple in DB tags if the file in each tuple really exist.
	 * if it doesn't the function delete the tuple from DB 
	 */
	public void deleteRemovedFileTags()
	{
		File sessionFile = new File("C:\\Program Files\\m&l_dropbox\\hibernate.cfg.xml");
		
		/*creating session for connecting to DB with hibernate*/
		SessionFactory sessionFactory = new Configuration().configure(sessionFile).buildSessionFactory(); 
		Session session = sessionFactory.openSession();
		
		TagManager manager = new TagManager(session);
		Query query = session.createQuery("from dbManager.Tag");
		@SuppressWarnings("unchecked")
		List <Tag>list = query.list();
		Iterator<Tag> it=list.iterator();
		Tag tag;
		String path;
		File f;
		while (it.hasNext())
		{
			/*for each tuple in the table tags we check if the file really exist */
			tag=it.next();
			path = tag.getPath();
			f = new File(path);
			//the file doesn't exist,therefore we remove this tuple from  DB
			if (!f.exists())
			{
				manager.deleteTag(tag);
			}			
		}
		session.flush();
		setChanged();
		//inform the GUI that there are tuples in DB which were deleted
		notifyObservers();
	}
}
